#include "ScenePrecompiled.hxx"
#include "SceneChild.hxx"

#include "InternalSceneChildImpl.hxx"
#include "InternalMeshContextImpl.hxx"
#include "InternalSceneFbx.hxx"

#include "SceneSkeletonContext.hxx"

Eifersucht::SceneChild::_Internal_impl::_Internal_impl( _In_ Eifersucht::SceneChildPtr _pRef ) : 
	_m_pNode(nullptr),
	_m_pRef(_pRef)
{
}

Eifersucht::SceneChild::_Internal_impl::~_Internal_impl()
{
	Safe_Destroy(_m_pNode);
}

void Eifersucht::SceneChild::_Internal_impl::_Cpy_all()
{
	_Cpy_name();
	_Cpy_transformation();
}

void Eifersucht::SceneChild::_Internal_impl::_Cpy_name()
{
	if ( _m_pRef && _m_pNode ) _m_pRef->m_Name = _m_pNode->GetName();
}

namespace _Internal_implementation
{
	typedef Eifersucht::XformBase::PropertyCache::ChannelCache::CurveCache::JointCollection _Curve_joint_collection;
	typedef Eifersucht::XformBase::PropertyCache::ChannelCache::CurveCache::Joint _Curve_joint;
			
#define _Internal_scene_child_impl_print_separator PrintToDebugWindowOnly8("------------------------------------------------------");

	static void _gs_Print_curve( 
		_In_ Eifersucht::XformBase::PropertyCache::ChannelCache::CurveCachePtr cache,
		_In_ Eifersucht::String8 const &header
		)
	{
		using namespace Eifersucht;
		static char8 s_msg[1024];
		if (cache == nullptr) return;

		for (auto &knot : cache->Knots)
		{
			sprintf_s( 
				s_msg, "%s P[%-3.3f;%-3.3f] M[%-3.3f;%-3.3f] L[%-3.3f;%-3.3f]", header.c_str(),
				knot->Point.x, knot->Point.y,
				knot->Meets.x, knot->Meets.y,
				knot->Leaves.x, knot->Leaves.y 
				); 
			PrintToDebugWindowOnly8(s_msg);
		}
	}

	static void _gs_Print_property( 
		_In_ Eifersucht::XformBase::PropertyCachePtr prop,
		_In_ Eifersucht::String8 const &header
		)
	{
		using namespace Eifersucht;
		if (prop == nullptr) return;

		_Internal_scene_child_impl_print_separator
		auto channel = prop->Get<XformBase::PropertyCache::XChannel>();
		if (channel) for (auto curve : channel->Curves) 
		{
			_gs_Print_curve(curve.second, header + "x" );
			_Internal_scene_child_impl_print_separator;
		}

		channel = prop->Get<XformBase::PropertyCache::YChannel>();
		if (channel) for (auto curve : channel->Curves) 
		{
			_gs_Print_curve(curve.second, header + "y" );
			_Internal_scene_child_impl_print_separator;
		}

		channel = prop->Get<XformBase::PropertyCache::ZChannel>();
		if (channel) for (auto curve : channel->Curves) 
		{
			_gs_Print_curve(curve.second, header + "z" );
			_Internal_scene_child_impl_print_separator;
		}
	}

	static void _gs_Extract_data_from_curve( 
		_Inout_ Eifersucht::XformBase::PropertyCache::ChannelCache::CurveCachePtr thisCurve, 
		_In_ Eifersucht::Fbx::AnimCurvePtr nodeCurve 
		)
	{
		using namespace Eifersucht;
		if (thisCurve == nullptr || nodeCurve == nullptr) return;
		AssertWithMessage(thisCurve->Knots.size() == 0, L"_gs_Extract_data_from_curve: Curve already have joints.");

		auto const numKeys = _To_uint nodeCurve->KeyGetCount();
		thisCurve->Knots = _Curve_joint_collection(numKeys, nullptr);
		for (auto &j : thisCurve->Knots) j = new _Curve_joint();
		thisCurve->ReconnectKnots();

		for (auto keyId = 0u; keyId < numKeys; keyId++)
		{
			switch (nodeCurve->KeyGetInterpolation(numKeys - 1))
			{
			case FbxAnimCurveDef::eInterpolationConstant : thisCurve->Knots[keyId]->eType = _Curve_joint::Const; break;
			case FbxAnimCurveDef::eInterpolationLinear : thisCurve->Knots[keyId]->eType = _Curve_joint::Linear; break;
			case FbxAnimCurveDef::eInterpolationCubic : thisCurve->Knots[keyId]->eType = _Curve_joint::Cubic; break;
			}

			thisCurve->Knots[keyId]->Time(_To_single nodeCurve->KeyGetTime(keyId).GetSecondDouble()); // x
			thisCurve->Knots[keyId]->Value(nodeCurve->KeyGetValue(keyId)); // y
		}

		for (auto keyId = 0u; keyId < numKeys; keyId++)
		{
			if (thisCurve->Knots[keyId]->eType == _Curve_joint::Cubic)
			{
				_Curve_joint::UpdateLeft(thisCurve->Knots[keyId], 
					nodeCurve->KeyGetLeftTangentWeight(keyId), 
					nodeCurve->KeyGetLeftDerivative(keyId)
					);
				_Curve_joint::UpdateRight(thisCurve->Knots[keyId], 
					nodeCurve->KeyGetRightTangentWeight(keyId), 
					nodeCurve->KeyGetRightDerivative(keyId)
					);
			}
		}

	}

	// Returns false, if the property cache is empty, and the pointer can be deleted. Otherwise, 
	// at least one property channel is animated.
	static bool _gs_Extract_data_from_property( 
		_In_ Eifersucht::XformBase::PropertyCache::ChannelPairU64 &pair,
		_Inout_ Eifersucht::XformBase::PropertyCachePtr childProperty,
		_In_ Eifersucht::Fbx::AnimCurvePtr nodeXCurve,
		_In_ Eifersucht::Fbx::AnimCurvePtr nodeYCurve,
		_In_ Eifersucht::Fbx::AnimCurvePtr nodeZCurve
		)
	{
		using namespace Eifersucht;
		uint16 deleteProperty = false;

		if ( nodeXCurve )
		{
			childProperty->Set<XformBase::PropertyCache::XChannel>(new XformBase::PropertyCache::ChannelCache());
			auto channel = childProperty->Get<XformBase::PropertyCache::XChannel>();
			channel->Curves[pair] = new XformBase::PropertyCache::ChannelCache::CurveCache();
			_gs_Extract_data_from_curve(channel->Curves[pair], nodeXCurve);
		} 
		else
		{
			deleteProperty |= (XformBase::PropertyCache::XChannel + 1);
		}

		if ( nodeYCurve )
		{
			childProperty->Set<XformBase::PropertyCache::YChannel>(new XformBase::PropertyCache::ChannelCache());
			auto channel = childProperty->Get<XformBase::PropertyCache::YChannel>();
			channel->Curves[pair] = new XformBase::PropertyCache::ChannelCache::CurveCache();
			_gs_Extract_data_from_curve(channel->Curves[pair], nodeYCurve);
		} 
		else
		{
			deleteProperty |= (XformBase::PropertyCache::YChannel + 1);
		}

		if ( nodeZCurve )
		{
			childProperty->Set<XformBase::PropertyCache::ZChannel>(new XformBase::PropertyCache::ChannelCache());
			auto channel = childProperty->Get<XformBase::PropertyCache::ZChannel>();
			channel->Curves[pair] = new XformBase::PropertyCache::ChannelCache::CurveCache();
			_gs_Extract_data_from_curve(channel->Curves[pair], nodeZCurve);
		} 
		else
		{
			deleteProperty |= (XformBase::PropertyCache::ZChannel + 1);
		}

		return deleteProperty != 0;
	}

	void _gs_Extract_transformation_data(
		_Inout_ Eifersucht::XformBasePtr transformationData,
		_In_ Eifersucht::Fbx::NodePtr nativeNode 
		)
	{
		using namespace Eifersucht;
	
		if ( !transformationData || !nativeNode ) 
		{
			AssertWithMessage(false, L"Eifersucht::XformHelper::_Extract_transformation_data: Nullptr provided.");
			return;
		}
		{
			Reign::Vector3 _Temp_3vector; // Declate temp variable to hold the tranformation data

			// Maya stuff :  

			/*
			_Cast_vector3(_Temp_3vector, nativeNode->TranslationMax.Get()); transformationData->LclTranslationMax = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->TranslationMin.Get()); transformationData->LclTranslationMin = _Temp_3vector;

			_Cast_vector3(_Temp_3vector, nativeNode->ScalingMax.Get()); transformationData->LclScaleMax = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->ScalingMin.Get()); transformationData->LclScaleMin = _Temp_3vector;

			_Cast_vector3(_Temp_3vector, nativeNode->RotationOffset.Get()); transformationData->LclRotationOffset = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->RotationPivot.Get()); transformationData->LclRotationPivot = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->PostRotation.Get()); transformationData->LclPostRotation = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->PreRotation.Get()); transformationData->LclPreRotation = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->RotationMax.Get()); transformationData->LclRotationMax = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->RotationMin.Get()); transformationData->LclRotationMin = _Temp_3vector;
			*/

			transformationData->LclRotationOrder = (TransformationBase::RotationOrder)(nativeNode->RotationOrder.Get());

			_Cast_vector3(_Temp_3vector, nativeNode->GeometricTranslation.Get()); transformationData->GeometricRotation = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->GeometricRotation.Get()); transformationData->GeometricRotation = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->GeometricScaling.Get()); transformationData->GeometricScale = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->LclTranslation.Get()); transformationData->LclTranslation = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->LclRotation.Get()); transformationData->LclRotation = _Temp_3vector;
			_Cast_vector3(_Temp_3vector, nativeNode->LclScaling.Get()); transformationData->LclScale = _Temp_3vector;
		}

		auto scene = nativeNode->GetScene();
		auto numStacks = _To_uint scene->GetMemberCount<FbxAnimStack>();
		for (decltype(numStacks) takeId = 0; takeId < numStacks; takeId++)
		{
			auto stack = scene->GetMember<FbxAnimStack>(takeId);
			auto numLayers = _To_uint stack->GetMemberCount<FbxAnimLayer>();
			for (decltype(numLayers) layerId = 0; layerId < numLayers; layerId ++)
			{
				TransformationBase::PropertyCache::ChannelCache::TakeLayerPair pair;
				pair.TakeLayerId(layerId);
				pair.TakeId(takeId);

				auto layer = stack->GetMember<FbxAnimLayer>(layerId);
				auto deletePropertyCache = false;

				// Geometric
			
				transformationData->GeometricTranslationCache = new XformBase::PropertyCache();
				deletePropertyCache = _gs_Extract_data_from_property( pair, transformationData->GeometricTranslationCache, 
					nativeNode->GeometricTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X),
					nativeNode->GeometricTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y),
					nativeNode->GeometricTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z)
					); if (deletePropertyCache) _Delete_pointer(transformationData->GeometricTranslationCache);
				transformationData->GeometricRotationCache = new XformBase::PropertyCache();
				deletePropertyCache = _gs_Extract_data_from_property( pair, transformationData->GeometricRotationCache, 
					nativeNode->GeometricRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X),
					nativeNode->GeometricRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y),
					nativeNode->GeometricRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z)
					); if (deletePropertyCache) _Delete_pointer(transformationData->GeometricRotationCache);
				transformationData->GeometricScaleCache = new XformBase::PropertyCache();
				deletePropertyCache = _gs_Extract_data_from_property( pair, transformationData->GeometricScaleCache, 
					nativeNode->GeometricScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X),
					nativeNode->GeometricScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y),
					nativeNode->GeometricScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z)
					); if (deletePropertyCache) _Delete_pointer(transformationData->GeometricScaleCache);

				// Local

				transformationData->LclTranslationCache = new XformBase::PropertyCache();
				deletePropertyCache = _gs_Extract_data_from_property( pair, transformationData->LclTranslationCache, 
					nativeNode->LclTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X),
					nativeNode->LclTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y),
					nativeNode->LclTranslation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z)
					); if (deletePropertyCache) _Delete_pointer(transformationData->LclTranslationCache);
				transformationData->LclRotationCache = new XformBase::PropertyCache();
				deletePropertyCache = _gs_Extract_data_from_property( pair, transformationData->LclRotationCache, 
					nativeNode->LclRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X),
					nativeNode->LclRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y),
					nativeNode->LclRotation.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z)
					); if (deletePropertyCache) _Delete_pointer(transformationData->LclRotationCache);
				transformationData->LclScaleCache = new XformBase::PropertyCache();
				deletePropertyCache = _gs_Extract_data_from_property( pair, transformationData->LclScaleCache, 
					nativeNode->LclScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_X),
					nativeNode->LclScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Y),
					nativeNode->LclScaling.GetCurve(layer, FBXSDK_CURVENODE_COMPONENT_Z)
					); if (deletePropertyCache) _Delete_pointer(transformationData->LclScaleCache);

				_gs_Print_property(transformationData->GeometricTranslationCache, "GT");
				_gs_Print_property(transformationData->GeometricRotationCache, "GR");
				_gs_Print_property(transformationData->GeometricScaleCache, "GS");
				_gs_Print_property(transformationData->LclTranslationCache, "T");
				_gs_Print_property(transformationData->LclRotationCache, "R");
				_gs_Print_property(transformationData->LclScaleCache, "S");
			}
		}
	}
}

void Eifersucht::SceneChild::_Internal_impl::_Cpy_transformation()
{
	using namespace _Internal_implementation;
	_gs_Extract_transformation_data(
		_m_pRef, _m_pNode
		);
}

void Eifersucht::SceneChild::_Internal_impl::_Assign_skeleton_recursively()
{
	static _Short_dbg_msg s_szMsg;

	if ( !_m_pRef || !_m_pNode ) return;		// Terminate assignment for this tree node
	auto nNodes = _m_pNode->GetChildCount();	// Number of children nodes (non-recursive)
	for ( decltype(nNodes) iNode = 0; iNode < nNodes; iNode++ ) // Loop through all the nodes
	{
		// The new child that should be bound to scene graph

		auto pNode = _m_pNode->GetChild(iNode); // The node that should be bound to the new child 
		auto pAttr = pNode->GetNodeAttribute();
		auto eAttr = pAttr ? pAttr->GetAttributeType() : Fbx::NodeAttribute::EType::eUnknown;
		if ( pAttr )
		{
			switch (eAttr)
			{

			case Fbx::NodeAttribute::eSkeleton:
				{
					PrintToCharArray(s_szMsg, "Eifersucht. InternalSceneImpl. New skeleton = (%s).", pNode->GetName()); 
					PrintToDebugWindowOnly8(s_szMsg);

					auto pChild = new Eifersucht::SceneChild(_m_pRef); // Child
					pChild->m_pParentScene = _m_pRef->m_pParentScene;
					pChild->m_pImpl->_m_pNode = pNode; // Assign
					pChild->m_pImpl->_Cpy_all(); // Copy all info
					pChild->m_pImpl->_Assign_skeleton_recursively(); // Proceed

					_m_pRef->m_Children.push_back( pChild ); // Append

					auto pSkeleton = new Eifersucht::SkeletonContext();
					pChild->m_pContext = pSkeleton;
					pSkeleton->m_pRef = pChild;

				} break;

			};
		}
	}
}

void Eifersucht::SceneChild::_Internal_impl::_Assign_mesh_recursively()
{
	static _Short_dbg_msg s_szMsg;

	if ( !_m_pRef || !_m_pNode ) return;		// Terminate assignment for this tree node
	auto nNodes = _m_pNode->GetChildCount();	// Number of children nodes (non-recursive)
	for ( decltype(nNodes) iNode = 0; iNode < nNodes; iNode++ ) // Loop through all the nodes
	{
		// The new child that should be bound to scene graph

		auto pNode = _m_pNode->GetChild(iNode); // The node that should be bound to the new child 
		auto pAttr = pNode->GetNodeAttribute();
		auto eAttr = pAttr ? pAttr->GetAttributeType() : Fbx::NodeAttribute::EType::eUnknown;
		if ( pAttr )
		{
			switch (eAttr)
			{

			case Fbx::NodeAttribute::eMesh:
				{ 
					PrintToCharArray(s_szMsg, "Eifersucht. InternalSceneImpl. New mesh = (%s).", pNode->GetName()); 
					PrintToDebugWindowOnly8(s_szMsg);

					auto pChild = new Eifersucht::SceneChild(_m_pRef); // Child
					pChild->m_pParentScene = _m_pRef->m_pParentScene;
					pChild->m_pImpl->_m_pNode = pNode; // Assign
					pChild->m_pImpl->_Cpy_all(); // Copy all info
					pChild->m_pImpl->_Assign_mesh_recursively(); // Proceed

					auto pMesh = new Eifersucht::MeshContext();
					pChild->m_pContext = pMesh;
					pMesh->m_pImpl->_m_pAttr = FbxCast<Fbx::Mesh>(pNode->GetNodeAttribute());
					pMesh->m_pRef = pChild;
					pMesh->m_pImpl->_Cpy_all();

					_m_pRef->m_Children.push_back( pChild ); // Append

				} break;

			}
		}
	}
}
